#include "Socket.h"

bool Socket::winsockStarted = false;

Socket::Socket(SOCKET sockfd)
{
	instance = new SocketInstance(sockfd);
}

Socket::Socket(): instance(0)
{
	SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);

	if (sockfd == -1)
	{
		throw SocketException();
	}

	instance = new SocketInstance(sockfd);
}

Socket::Socket(const Socket& rhs): instance(rhs.instance)
{
	if (instance)
		instance->IncreaseReference();
}

Socket::~Socket()
{
	if (instance)
	{
		instance->DecreaseReference();

		if (instance->GetReferenceCount() == 0)
		{
			Close();

			delete instance;
		}

		instance = 0;
	}
}


SOCKET Socket::GetSocket() const
{
	return instance->GetSocket();
}

void Socket::Close()
{
	if (!instance->IsClosed())
	{
		closesocket( instance->GetSocket() );

		instance->Close();
	}
}

bool Socket::IsClosed() const
{
	return instance->IsClosed();
}


int Socket::Send(const void *buf, unsigned int len)
{
	const char *bytebuf = (const char*)buf;
    unsigned int total = 0;
    unsigned int bytesleft = len;
    int n = 0;

    while(total < len) 
	{
		
        n = send(instance->GetSocket(), bytebuf, bytesleft, 0);

        if (n == -1)
			break;

		bytebuf += n;
        total += n;
        bytesleft -= n;
    }


	if (n == -1)
		return n;
	else
		return 0;
}

int Socket::Receive(void *buf, unsigned int size)
{
	char *bytebuf = (char *)buf;
	int readbytes;

    readbytes = recv(instance->GetSocket(), bytebuf, size, 0);

	if (readbytes == 0)
	{
		Close();
	}
	else if (readbytes == -1)
		throw SocketException();

	return readbytes;
}

void Socket::Startup()
{
	WORD wVersionRequested;
    WSADATA wsaData;
	int err;

	if (!winsockStarted)
	{

		wVersionRequested = MAKEWORD(2, 2);

		err = WSAStartup(wVersionRequested, &wsaData);
		if (err != 0) 
		{
			throw SocketException(err);
		}

		winsockStarted = true;
	}
}

void Socket::Cleanup()
{
	if (winsockStarted)
	{
		WSACleanup();
		winsockStarted = false;
	}
}

unsigned int Socket::ReceiveLeft() const
{
	u_long size = 0;

	if (ioctlsocket(GetSocket(), FIONREAD, &size) != 0)
		throw SocketException();

	return size;
}

std::string Socket::PeerName() const
{
	struct sockaddr_in addr;
	int len = sizeof(addr);

	getpeername(GetSocket(), (struct sockaddr*)&addr, &len);

	return inet_ntoa(addr.sin_addr);
}

unsigned short Socket::PeerPort() const
{
	struct sockaddr_in addr;
	int len = sizeof(addr);

	getpeername(GetSocket(), (struct sockaddr*)&addr, &len);

	return ntohs(addr.sin_port);
}
